# -*- coding: utf8 -*-
# !/usr/bin/env python


""" 文档说明 V0.1 : 
# 使用 Airtest - poco 框架, 来抓取 网易云音乐 APP - 抖音热门歌单排放榜。
# 检索 keyword = "抖音排行榜" 中 Top-100 热门音乐 的相关数据(包括: 相关歌曲，歌手，排行榜的订阅，评论，分享数等)

@version : 0.1
@author  : Knight
@email   : xxxxxx@mail.com
@Created : 2023-05-10 11:36:27
@Project : NeteaseCloudMusicApp - Airtest
@Module  : netease_cloud_music.py
@Modified: 

"""


import datetime
import pprint
import random

from airtest.cli.parser import cli_setup
from airtest.core.api import *
from airtest.core.android import Android
# 使用 Windows Shell 的 API, 执行 shell 命令
from airtest.core.win import Windows
from airtest.core.error import TargetNotFoundError
from airtest.report.report import simple_report

from poco.drivers.android.uiautomation import AndroidUiautomationPoco


if not cli_setup():
    auto_setup(__file__, 
               logdir=True, 
               devices=["android://127.0.0.1:5037/127.0.0.1:7555?cap_method=JAVACAP&touch_method=MAXTOUCH&",],
               project_root="E:/Workspace/pythonProject/Airtest")


# generate html report
# from airtest.report.report import simple_report
simple_report(__file__, logpath=True)


# Tips：应先连接设备，再打开APP，最后才初始化poco，可以有效避免一些奇怪的错误.

# 设置全局的随机等待时长
global_random_delay = random.randrange(4, 10) * 0.5


def connect_multi_devices(default_host="127.0.0.1:5037", 
                          sep_symbol="-", 
                          sep_length=80):
    """ 建立与当前主机已连接的所有设备(通过 adb shell 命令方式)，并打印相关设备的信息(连接地址,
        屏幕参数信息等).
        @default_host: 默认主机连接地址 "127.0.0.1:5037"
        @sep_symbol: 分割符号
        @sep_length: 分割符号长度
        @Return: dict 类型容器    
    """
    
    # 收集连接设备device, poco对象的容器
    container = dict()
    
    # 分割线
    sep_line = sep_symbol * sep_length
    
    # 通过 adb shell 获取与当前主机连接的所有Android设备信息
    conn_devices = Windows().shell("adb devices").decode("utf-8").strip()
    devices_list = [_dev.split("\t")[0] for _dev in conn_devices.split("\n")[1:]]
  
    # 模拟开始建立设别连接
    log_record_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    print(f"[{log_record_at}] 开始建立连接设备连接...\n{sep_line}")
    
    for _no, serialno in enumerate(devices_list, start=1):
        # 构建连接参数信息
        _device_no = f"device_{_no:03d}"
        conn_address = f"android://{default_host}/{serialno}?cap_method=JAVACAP&touch_method=MAXTOUCH&"

        # 建立连接，
        log_record_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        # [1.1] 连接设备
        connected_device = connect_device(conn_address)
        # [1.2] 清除 App 数据 // 启动 APP
        
        # [1.3] 实例化Poco驱动
        poco = AndroidUiautomationPoco(device=connected_device,
                                                 using_proxy=True, 
                                                 force_restart=False,
                                                 use_airtest_input=False,
                                                 screenshot_each_action=False)   
        # 收集 poco, device 实例对象
        container[_device_no] = {"poco": poco, "device": connected_device}
        
        # 获取屏幕参数信息(宽度,高度,密度，方向，旋转,分辨率等)
        screen_info = connected_device.get_display_info()
        # screenWidth, screenHeight = poco.get_screen_size()
        
        # 打印相关设备信息
        log_record_at = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"[{log_record_at}] 设备编号: {_device_no}, 序列号: {serialno}, 屏幕信息: {screen_info}")
        print(sep_line)
        
    return container


def show_connected_device_infos():
    """ 展示与主机建立连接的所有设备信息"""

    print("展示与主机建立连接的所有设备信息: ")
    for _no, conn_device in enumerate(G.DEVICE_LIST, start=1):
        # 当前连接设备信息
        serialno = conn_device.__dict__.get("serialno")
        ip_address = conn_device.get_ip_address()

        # 打印设备信息
        print(f"序号: {_no:03d}, 设备序列号: {serialno}, 设备IP地址: {ip_address}")

        
def random_swipe_up(random_swipe_delay=(1, 10), ratio=0.55):
    """ 设置随机滑动, 由下至上随机;
    @random_swipe_delay: 设置滑动后随机等待延时区间: [1, 10] 秒.
    @ratio: 模拟人工等待时长的干扰项.
    """
    
    begin_x = random.randrange(40, 60) * 0.01
    end_x = random.randrange(76, 85) * 0.01
    begin_y = random.randrange(40, 60) * 0.01
    end_y = random.randrange(46, 60) * 0.01
    
    # 随机滑动(指定范围内)
    swipe([begin_x, end_x], [begin_y, end_y], duration=1.2)

    # 设置随机等待
    swipe_delay = random.randrange(*random_swipe_delay) * ratio
    sleep(swipe_delay)


# 设备连接信息
conn_device_container = connect_multi_devices()
device_info = conn_device_container.get("device_001")
device = device_info.get("device")
poco = device_info.get("poco")


# 唤醒设备
device.wake()

# 检查设备上已经安装的第三方APP
# installed_apps = device.list_app(third_only=True)

# App info
app_package = "com.netease.cloudmusic"


# 检查 App 是否已经安装
if device.check_app(app_package):
    # 清除 App 缓存数据
    device.clear_app(app_package)
    # 直接启动 App
    device.start_app(app_package)
else:
    # 若未安装 App
    raise TargetNotFoundError(f"Not install app: {app_package} yet, please mannual install.")

# 启动 App 后, 等待<用户条款和隐私政策提示>页面, 点击 "同意"
poco(name="com.netease.cloudmusic:id/title", text="用户条款和隐私政策提示").wait_for_appearance(timeout=60.0)
poco(name="com.netease.cloudmusic:id/agree", text="同意").click()

# 点击 "立即体验"
# poco(name="com.netease.cloudmusic:id/loginMarketing").wait_for_appearance(timeout=60.0)
poco(resourceId="com.netease.cloudmusic:id/trialT").wait(timeout=60.0).click()


# 点击控件 - 关闭软件提示页面，进行登录
poco(name="com.netease.cloudmusic:id/rootViewBg").wait_for_appearance(timeout=30.0)
poco(name="com.netease.cloudmusic:id/positiveBtn", text="同意并继续").click()

sleep(10.0)


# if poco(name="android.widget.Image", textMatches=r"close-v2.*").exists():
#     # 如果APP显示 "去登陆页面"，关闭不进行登录
#     poco(name="android.widget.TextView", text="去登录").wait(timeout=10.0)

# 点击 搜索 Banner
# poco(name="androidx.recyclerview.widget.RecyclerView").wait(timeout=30.0)

# if poco(name="com.netease.cloudmusic:id/dsl_dialog_root").exists():
#     # 弹出 “去登录”的对话框, 则点击其它地方
#     poco(name="com.netease.cloudmusic:id/touch_outside").click()

poco(name="com.netease.cloudmusic:id/searchHintText").wait(timeout=60.0).click()


# 搜索 "抖音", 并点击搜索
music_topic = "抖音排行榜"
poco(resourceId="com.netease.cloudmusic:id/search_src_text").wait(timeout=10.0).set_text(music_topic)
poco(resourceId="com.netease.cloudmusic:id/toSearch", text="搜索").wait(timeout=6.0).click()

# 等待响应结果出现
poco(name="com.netease.cloudmusic:id/tabLayout").wait_for_appearance(timeout=30.0)
poco(name="com.netease.cloudmusic:id/recyclerView").wait_for_appearance(timeout=120.0)
poco(name="android.widget.TextView", text="歌单").wait(timeout=5.0).click()

# 点击 "抖音排行榜"
poco(name="com.netease.cloudmusic:id/recyclerView").wait_for_appearance(timeout=180.0)


if poco(name="com.netease.cloudmusic:id/retry_btn").exists():
    # 点击刷次重试
    retry_path =  poco(name="com.netease.cloudmusic:id/retry_btn")
    if "重试" in retry_path.attr("text"):
        # 点击 - 重新刷新
        retry_path.wait(timeout=3.0).click()

# 点击对应 "音乐主题榜单"
poco(name="com.netease.cloudmusic:id/name", text=music_topic).wait(timeout=10.0).click()


def collect_page_items(poco):
    """ 解析并返回页面数据. 
        Tips: 因部分页面中的数据会因滑动造成数据分割到不同页面，因此需要分割页面处理
    """

    # 统计当页面的数据
    page_container = dict()
    # 展示歌单表单的路径
    music_list_path = poco("android.widget.FrameLayout").child("android.widget.LinearLayout").offspring("com.netease.cloudmusic:id/musicInfoList")

    for _path in music_list_path.children():      
        # Music info
        # rank
        if _path.offspring("com.netease.cloudmusic:id/songRank"):
            rank = _path.offspring("com.netease.cloudmusic:id/songRank").get_text()
        else:
            # 本页面的检索 music - item 结束标志
            continue

        # song name
        if _path.offspring("com.netease.cloudmusic:id/songName").exists():
            name = _path.offspring("com.netease.cloudmusic:id/songName").get_text()
        else:
            name = "SongName"
        
        # author info
        if _path.offspring("com.netease.cloudmusic:id/songInfoContainer").exists():
            _path_author = _path.offspring("com.netease.cloudmusic:id/songInfoContainer")
            if _path_author:
                if _path_author.child("com.netease.cloudmusic:id/songInfo"):
                    author = _path_author.child("com.netease.cloudmusic:id/songInfo").get_text()
                else:
                    author = "Author-Song1"
            else:
                author = "Author-Song2"
        else:
            author = "Author-Song3"

        # 收集音乐信息(使用rank 作为唯一ID进行更新)
        music_rank_no = f"{rank:>03s}"
        music_info = f"RankNo: {music_rank_no:>03s}, Name: {name}, Info: {author}"
        page_container[music_rank_no] = music_info

    return page_container
    

def summary_stat_data(poco):
    """ 统计 "抖音排行版" 的相关信息."""

    # 首页基础统计: 分享, 评论, 订阅
    shares = poco(name="com.netease.cloudmusic:id/shareNum").attr("text")
    comments = poco(name="com.netease.cloudmusic:id/commentNum").attr("text")
    subscribes = poco(name="com.netease.cloudmusic:id/subscribeNum").attr("text")    
    
    # 收集音乐列表
    base_info = f"Share: {shares}, Comment: {comments}, Subscribe: {subscribes}."
    music_container = {
                        "base": base_info,
                        "data": dict(),
                        "desc": "TikTok Music Tops Rank From Netease Music Cloud."
                      }

    while True:
        # 收集数据
        music_items =  collect_page_items(poco)
        music_container["data"].update(music_items)

        # 设置滑动停止条件(页面出现推荐: 你可能还会喜欢)
        if poco(name="com.netease.cloudmusic:id/rcmdPlaylistTitle", textMatches=r".*喜欢.*"):
            # 停止条件2: 停止滑动，收集信息后，停止循环
            print("触发停止收集条件1：你可能还会喜欢.")
            break
        elif poco(name="com.netease.cloudmusic:id/playListSubscriberView"):
            # 设置停止条件2：
            if poco(name="com.netease.cloudmusic:id/playListSubscriberView").attr("visible"):
                print("触发停止收集条件2： 出现订阅栏.")
                break
        else:
            # 模拟向上滑动
            random_swipe_up()
    
    return music_container


# 收集数据, 并打印数据
stat_data = summary_stat_data(poco)
pprint.pprint(stat_data)


# 模拟"后退操作", KEYNAME: HOME, BACK, KEYCODE_DEL, KEYCODE_MENU, KEYCODE_WAKEUP
keyevent("BACK")

# [3.6] 关闭 APP
sleep(global_random_delay)
stop_app(app_package)

# [3.7] 退回系统首页
sleep(global_random_delay)
keyevent("HOME")

# ---------------------------------------------------------------------------------
# 网易云登录方案:

# 方式一: 使用手机号码，解决方案：使用打码平台的手机号码+短信验证码进行登录(推荐:使用Android 真机)
# # 获取三方打码平台的短信信息
# from .sms_api import PlatformSupportAPI, get_code_from_msg

# # 验证码: 打码平台
# phone = "17154238479"

# # 等待手机登录页面出现(默认: 手机号码+验证码的登录方式)
# poco(name="com.netease.cloudmusic:id/cellphone", textMatches=r"输入手机号.*").wait_for_appearance(timeout=10.0).set_text(phone)

# # 点击 "同意条款"
# sleep(3.0)
# poco(name="com.netease.cloudmusic:id/agree").wait(timeout=2.0).click()

# # 点击 "验证码登录"
# poco(name="com.netease.cloudmusic:id/newLoginBtn").wait(timeout=3.0).click()

# # 等待打码平台返回验证码信息(通常60s之内返回)
# poco.sleep(55.0)

# # 获取验证码
# sms_api = PlatformSupportAPI(phone=phone, sms_keyword="验证码")
# verify_code = sms_api.get_code_from_msg()

# # 若验证码通过(因为打码平台手机号码质量问题, 可能需要进行人工申诉)Tips: 填写申诉手机号码后，可使用24 Hours
# # 此步骤省略

# # 输入验证码
# poco(name="com.netease.cloudmusic:id/captchaInput").set_text(verify_code)

# # 等待自动登录
# sleep(10.0)

# 方式二: 采用邮箱登录方式(此方案：设备检测, 推荐使用真机登录)
# poco(name="com.netease.cloudmusic:id/thirdLoginTextView", text="其他登录方式").click()

# sleep(3.0)

# # 点击邮箱
# poco(name="com.netease.cloudmusic:id/externalLogin").wait_for_appearance(timeout=10.0)
# poco(resourceId="com.netease.cloudmusic:id/mail").click()

# sleep(2.5)

# poco(name="com.netease.cloudmusic:id/positiveBtn").wait(timeout=2.0).click()

# # 网易云邮箱: 账户+密码
# account = "xxxxxxxxxx@163.com"
# password = "xxxxxxxxxxxx"

# poco(name="com.netease.cloudmusic:id/email").set_text(account)
# poco(name="com.netease.cloudmusic:id/password").set_text(password)

# # 点击登录
# poco(name="com.netease.cloudmusic:id/login").wait(timeout=1.5).click()

# sleep(6.0)
# ---------------------------------------------------------------------------------
